#include "triangle.h"
#include <QDateTime>

#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
GLuint VBO, VAO, lightVAO;

const char *vertexShaderSource =
        "#version 330 core\n"
        "layout (location = 0) in vec3 aPos;"
        "layout (location = 1) in vec3 aNormal;"
        "layout (location = 2) in vec2 aTexCoords;"
        "out vec3 FragPos;"
        "out vec3 Normal;"
        "out vec2 TexCoords;"

        "uniform mat4 model;"
        "uniform mat4 view;"
        "uniform mat4 projection;"
        "void main()"
        "{"
        "   FragPos = vec3(model * vec4(aPos, 1.0));"
        "   gl_Position = projection * view * vec4(FragPos, 1.0);"
        "   Normal = mat3(transpose(inverse(model))) * aNormal;"
        "   TexCoords = aTexCoords;"
        "}";

const char *fragmentShaderSource =
        "#version 330 core\n"
        "struct Material {"
        "    sampler2D diffuse;" //漫反射光照材质向量定义了在漫反射光照下物体的颜色
        "    sampler2D specular;"  //镜面光照材质向量设置的是镜面光照对物体的颜色影响
        "    float shininess;" //反光度影响镜面高光的散射/半径
        "};"

        "struct DirLight {"
        "    vec3 direction;"

        "    vec3 ambient;"
        "    vec3 diffuse;"
        "    vec3 specular;"
        "};"

        "struct PointLight {"
        "    vec3 position;"

        "    float constant;"
        "    float linear;"
        "    float quadratic;"

        "    vec3 ambient;"
        "    vec3 diffuse;"
        "    vec3 specular;"
        "};\n"
        "#define NR_POINT_LIGHTS 4\n"

        "struct SpotLight {"
        "    vec3 position;"
        "    vec3 direction;"
        "    float cutOff;"
        "    float outerCutOff;"
        "    float constant;"
        "    float linear;"
        "    float quadratic;"

        "    vec3 ambient;"
        "    vec3 diffuse;"
        "    vec3 specular;"
        "}; "

        "out vec4 FragColor;"
        "in vec3 Normal;"
        "in vec3 FragPos;"
        "in vec2 TexCoords;"
        "uniform vec3 viewPos;"
        "uniform Material material;"

        "uniform DirLight dirLight;"
        "uniform PointLight pointLights[NR_POINT_LIGHTS];"
        "uniform SpotLight spotLight;"

        "vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir);"
        "vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir);"
        // calculates the color when using a spot light.
        "vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir);"
        "void main(){"
        // 属性
        "    vec3 norm = normalize(Normal);"
        "    vec3 viewDir = normalize(viewPos - FragPos);"

        // 第一阶段：定向光照
        "    vec3 result = CalcDirLight(dirLight, norm, viewDir);"
        // 第二阶段：点光源
        "    for(int i = 0; i < NR_POINT_LIGHTS; i++)"
        "        result += CalcPointLight(pointLights[i], norm, FragPos, viewDir);    "
        // 第三阶段：聚光
        "    result += CalcSpotLight(spotLight, norm, FragPos, viewDir);    "

        "    FragColor = vec4(result, 1.0);"
        "}"

        "vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir)"
        "{"
        "    vec3 lightDir = normalize(-light.direction);"
        // 漫反射着色
        "    float diff = max(dot(normal, lightDir), 0.0);"
        // 镜面光着色
        "    vec3 reflectDir = reflect(-lightDir, normal);"
        "    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);"
        // 合并结果
        "    vec3 ambient  = light.ambient  * vec3(texture(material.diffuse, TexCoords));"
        "    vec3 diffuse  = light.diffuse  * diff * vec3(texture(material.diffuse, TexCoords));"
        "    vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));"
        "    return (ambient + diffuse + specular);"
        "}"

        "vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir)"
        "{"
        "    vec3 lightDir = normalize(light.position - fragPos);"
        // 漫反射着色
        "    float diff = max(dot(normal, lightDir), 0.0);"
        // 镜面光着色
        "    vec3 reflectDir = reflect(-lightDir, normal);"
        "    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);"
        // 衰减
        "    float distance    = length(light.position - fragPos);"
        "    float attenuation = 1.0 / (light.constant + light.linear * distance + "
        "                 light.quadratic * (distance * distance));"
        // 合并结果
        "    vec3 ambient  = light.ambient  * vec3(texture(material.diffuse, TexCoords));"
        "    vec3 diffuse  = light.diffuse  * diff * vec3(texture(material.diffuse, TexCoords));"
        "    vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));"
        "    ambient  *= attenuation;"
        "    diffuse  *= attenuation;"
        "    specular *= attenuation;"
        "    return (ambient + diffuse + specular);"
        "}"

        "vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir)"
        "{"
        "    vec3 lightDir = normalize(light.position - fragPos);"
            // diffuse shading
        "    float diff = max(dot(normal, lightDir), 0.0);"
            // specular shading
        "    vec3 reflectDir = reflect(-lightDir, normal);"
        "    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);"
            // attenuation
        "    float distance = length(light.position - fragPos);"
        "    float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));"
            // spotlight intensity
        "    float theta = dot(lightDir, normalize(-light.direction)); "
        "    float epsilon = light.cutOff - light.outerCutOff;"
        "    float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0);"
            // combine results
        "    vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));"
        "    vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));"
        "    vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));"
        "    ambient *= attenuation * intensity;"
        "    diffuse *= attenuation * intensity;"
        "    specular *= attenuation * intensity;"
        "    return (ambient + diffuse + specular);"
        "}";

const char *lightColorVs =
        "#version 330 core\n"
        "layout (location = 0) in vec3 aPos;"   // 位置变量的属性位置值为 0
        "uniform mat4 model;"
        "uniform mat4 view;"
        "uniform mat4 projection;"

        "void main(){"
        "   gl_Position = projection * view * model * vec4(aPos, 1.0);"
        "}";

const char *lightColorFs =
        "#version 330 core\n"
        "out vec4 FragColor;"
        "void main()"
        "{"
        "   FragColor = vec4(1.0);"
        "}";

Triangle::Triangle(){

}

Triangle::~Triangle(){

}

void Triangle::initializeGL(){
    this->resize(1000,800);
    //着色器部分
    this->initializeOpenGLFunctions();
    // 监听键盘
    this->releaseKeyboard();
    // 开启深度测试
    glEnable(GL_DEPTH_TEST);

    shader = std::make_shared<Shader>(this);
    lightShader = std::make_shared<Shader>(this);

    shader->readShaderStr(vertexShaderSource,fragmentShaderSource);
    lightShader->readShaderStr(lightColorVs,lightColorFs);

    //VAO，VBO数据部分
    // ------------------------------------------------------------------
    float vertices[] = {
        // 位置          // 法向量           // 纹理位置
        -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f,  0.0f,
        0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f,  0.0f,
        0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f,  1.0f,
        0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f,  1.0f,
        -0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f,  1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f,  0.0f,

        -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  0.0f,  0.0f,
        0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  1.0f,  0.0f,
        0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  1.0f,  1.0f,
        0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  1.0f,  1.0f,
        -0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  0.0f,  1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  0.0f,  0.0f,

        -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  1.0f,  0.0f,
        -0.5f,  0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  1.0f,  1.0f,
        -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  0.0f,  1.0f,
        -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  0.0f,  1.0f,
        -0.5f, -0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  0.0f,  0.0f,
        -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  1.0f,  0.0f,

        0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  1.0f,  0.0f,
        0.5f,  0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  1.0f,  1.0f,
        0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  0.0f,  1.0f,
        0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  0.0f,  1.0f,
        0.5f, -0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  0.0f,  0.0f,
        0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  1.0f,  0.0f,

        -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  0.0f,  1.0f,
        0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  1.0f,  1.0f,
        0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  1.0f,  0.0f,
        0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  1.0f,  0.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  0.0f,  0.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  0.0f,  1.0f,

        -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  0.0f,  1.0f,
        0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  1.0f,  1.0f,
        0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  1.0f,  0.0f,
        0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  1.0f,  0.0f,
        -0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  0.0f,  0.0f,
        -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  0.0f,  1.0f
    };
    // 创建顶点数组对象
    glGenVertexArrays(1, &VAO);//两个参数，第一个为需要创建的缓存数量。第二个为用于存储单一ID或多个ID的GLuint变量或数组的地址
    // 绑定顶点缓冲区数组对象
    glBindVertexArray(VAO);

    // 创建顶点数组对象
    glGenBuffers(1, &VBO);
    // 绑定顶点缓冲区数组对象
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    // 立方体位置属性
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    // 点的法向量
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    // 纹理位置
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
    glEnableVertexAttribArray(2);

    glGenVertexArrays(1, &lightVAO);
    glBindVertexArray(lightVAO);

    // 灯光位置属性
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);

    // we only need to bind to the VBO (to link it with glVertexAttribPointer), no need to fill it; the VBO's data already contains all we need (it's already bound, but we do it again for educational purposes)
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    lightPos = glm::vec3(1.2f, 1.0f, 2.0f);

    // 纹理部分
    // 创建纹理
    glGenTextures(1, &texture1);
    glBindTexture(GL_TEXTURE_2D, texture1);
    // 为当前绑定的纹理对象设置环绕、过滤方式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    // 加载并生成纹理
    int width, height, nrChannels;
    unsigned char *data = stbi_load("D:\\Material\\container2.png", &width, &height, &nrChannels, 0);
    if (data)
    {
        // JPG GL_RGB;PNG GL_RGBA
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
        glGenerateMipmap(GL_TEXTURE_2D);
    }
    else
    {
        qDebug() << "Failed to load texture" << '\n';
    }
    stbi_image_free(data);
    glGenTextures(1, &texture2);
    glBindTexture(GL_TEXTURE_2D, texture2);
    // 为当前绑定的纹理对象设置环绕、过滤方式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    data = stbi_load("D:\\Material\\container2.png", &width, &height, &nrChannels, 0);
    if (data)
    {
        // JPG GL_RGB;PNG GL_RGBA
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
        glGenerateMipmap(GL_TEXTURE_2D);
    }
    else
    {
        qDebug() << "Failed to load texture" << '\n';
    }
    stbi_image_free(data);
    shader->use();
    shader->setInt("material.diffuse", 0);
    shader->setInt("material.specular", 1); // specular lighting doesn't have full effect on this object's material
    time.start();
}

void Triangle::resizeGL(int w, int h){
    QExpandOpenGLWidget::resizeGL(w, h);
}

void Triangle::paintGL(){
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // 位置模型
    glm::mat4 model = glm::mat4(1.0f);
    view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);

    static float trasnCount = 0.0f;
    shader->use();
    shader->setVec3("viewPos", cameraPos);
    // material properties
    shader->setFloat("material.shininess", 32.0f);
    // positions of the point lights
    glm::vec3 pointLightPositions[] = {
        glm::vec3( 0.7f,  0.2f,  2.0f),
        glm::vec3( 2.3f, -3.3f, -4.0f),
        glm::vec3(-4.0f,  2.0f, -12.0f),
        glm::vec3( 0.0f,  0.0f, -3.0f)
    };
    // directional light
    shader->setVec3("dirLight.direction", -0.2f, -1.0f, -0.3f);
    shader->setVec3("dirLight.ambient", 0.05f, 0.05f, 0.05f);
    shader->setVec3("dirLight.diffuse", 0.4f, 0.4f, 0.4f);
    shader->setVec3("dirLight.specular", 0.5f, 0.5f, 0.5f);
    // point light 1
    shader->setVec3("pointLights[0].position", pointLightPositions[0]);
    shader->setVec3("pointLights[0].ambient", 0.05f, 0.05f, 0.05f);
    shader->setVec3("pointLights[0].diffuse", 0.8f, 0.8f, 0.8f);
    shader->setVec3("pointLights[0].specular", 1.0f, 1.0f, 1.0f);
    shader->setFloat("pointLights[0].constant", 1.0f);
    shader->setFloat("pointLights[0].linear", 0.09f);
    shader->setFloat("pointLights[0].quadratic", 0.032f);
    // point light 2
    shader->setVec3("pointLights[1].position", pointLightPositions[1]);
    shader->setVec3("pointLights[1].ambient", 0.05f, 0.05f, 0.05f);
    shader->setVec3("pointLights[1].diffuse", 0.8f, 0.8f, 0.8f);
    shader->setVec3("pointLights[1].specular", 1.0f, 1.0f, 1.0f);
    shader->setFloat("pointLights[1].constant", 1.0f);
    shader->setFloat("pointLights[1].linear", 0.09f);
    shader->setFloat("pointLights[1].quadratic", 0.032f);
    // point light 3
    shader->setVec3("pointLights[2].position", pointLightPositions[2]);
    shader->setVec3("pointLights[2].ambient", 0.05f, 0.05f, 0.05f);
    shader->setVec3("pointLights[2].diffuse", 0.8f, 0.8f, 0.8f);
    shader->setVec3("pointLights[2].specular", 1.0f, 1.0f, 1.0f);
    shader->setFloat("pointLights[2].constant", 1.0f);
    shader->setFloat("pointLights[2].linear", 0.09f);
    shader->setFloat("pointLights[2].quadratic", 0.032f);
    // point light 4
    shader->setVec3("pointLights[3].position", pointLightPositions[3]);
    shader->setVec3("pointLights[3].ambient", 0.05f, 0.05f, 0.05f);
    shader->setVec3("pointLights[3].diffuse", 0.8f, 0.8f, 0.8f);
    shader->setVec3("pointLights[3].specular", 1.0f, 1.0f, 1.0f);
    shader->setFloat("pointLights[3].constant", 1.0f);
    shader->setFloat("pointLights[3].linear", 0.09f);
    shader->setFloat("pointLights[3].quadratic", 0.032f);
    // spotLight
    shader->setVec3("spotLight.position", cameraPos);
    shader->setVec3("spotLight.direction", cameraFront);
    shader->setVec3("spotLight.ambient", 0.0f, 0.0f, 0.0f);
    shader->setVec3("spotLight.diffuse", 1.0f, 1.0f, 1.0f);
    shader->setVec3("spotLight.specular", 1.0f, 1.0f, 1.0f);
    shader->setFloat("spotLight.constant", 1.0f);
    shader->setFloat("spotLight.linear", 0.09f);
    shader->setFloat("spotLight.quadratic", 0.032f);
    shader->setFloat("spotLight.cutOff", glm::cos(glm::radians(12.5f)));
    shader->setFloat("spotLight.outerCutOff", glm::cos(glm::radians(15.0f)));

    glm::vec3 cubePositions[] = {
        glm::vec3( 0.0f,  0.0f,  0.0f),
        glm::vec3( 2.0f,  5.0f, -15.0f),
        glm::vec3(-1.5f, -2.2f, -2.5f),
        glm::vec3(-3.8f, -2.0f, -12.3f),
        glm::vec3( 2.4f, -0.4f, -3.5f),
        glm::vec3(-1.7f,  3.0f, -7.5f),
        glm::vec3( 1.3f, -2.0f, -2.5f),
        glm::vec3( 1.5f,  2.0f, -2.5f),
        glm::vec3( 1.5f,  0.2f, -1.5f),
        glm::vec3(-1.3f,  1.0f, -1.5f)
    };
    shader->setVec3("light.direction", -0.2f, -1.0f, -0.3f);
    shader->setMat4("projection", projection);
    shader->setMat4("view", view);

    // bind diffuse map
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture1);

    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, texture2);
    glBindVertexArray(VAO);
    for(unsigned int i = 0; i < 10; i++)
    {
        model = glm::translate(glm::mat4(1.0f), cubePositions[i]);
        float angle = 20.0f * i;
        model = glm::rotate(model, trasnCount * glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
        shader->setMat4("model", model);
        glDrawArrays(GL_TRIANGLES, 0, 36);
    }
    trasnCount+=0.01f;


    // also draw the lamp object(s)
    lightShader->use();
    lightShader->setMat4("projection", projection);
    lightShader->setMat4("view", view);

    // we now draw as many light bulbs as we have point lights.
    glBindVertexArray(lightVAO);
    for (unsigned int i = 0; i < 4; i++)
    {
        model = glm::mat4(1.0f);
        model = glm::translate(model, pointLightPositions[i]);
        model = glm::scale(model, glm::vec3(0.2f)); // Make it a smaller cube
        lightShader->setMat4("model", model);
        glDrawArrays(GL_TRIANGLES, 0, 36);
    }

    glUseProgram(0);
    this->update();
}

void Triangle::keyPressEvent(QKeyEvent *event){
    QExpandOpenGLWidget::keyPressEvent(event);
}

void Triangle::mouseReleaseEvent(QMouseEvent* event){
    QExpandOpenGLWidget::mouseReleaseEvent(event);
}


void Triangle::mousePressEvent(QMouseEvent *event) {
    QExpandOpenGLWidget::mousePressEvent(event);
}

void Triangle::mouseMoveEvent(QMouseEvent* event){
    QExpandOpenGLWidget::mouseMoveEvent(event);
}


void Triangle::wheelEvent(QWheelEvent *event) {
    QExpandOpenGLWidget::wheelEvent(event);
}
